#include <iostream>
#include <mutex>
#include <cassert>

#include <Windows.h>

#include "Window.hpp"
#include "Input.hpp"
#include "Event.hpp"

#include "Debug.hpp"

LRESULT CALLBACK CWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	CWindow* window = nullptr;

	if(uMsg != WM_CREATE)
	{
		window = reinterpret_cast<CWindow*>(::GetWindowLongPtr(hWnd, 0));
		if(window != nullptr)
			return window->MsgHandler(hWnd, uMsg, wParam, lParam);
	}
	else
	{
		window = static_cast<CWindow*>(((LPCREATESTRUCT)lParam)->lpCreateParams);
		assert(window != nullptr);
	
		::SetWindowLongPtr(hWnd, 0, (LONG_PTR)window);
	}

	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK CWindow::MsgHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
#if 0
	case WM_SYSCOMMAND:
		switch(wParam)
		{
			// Screensaver trying to start or monitor trying to enter powersave?
		case SC_SCREENSAVE:
		case SC_MONITORPOWER:
			//if( fsMonitor )
			//	return 0;
			break;
			// User trying to access application menu using ALT?
		case SC_KEYMENU:
			return 0;
		}
		break;
#endif

	case WM_ERASEBKGND: // Stop flickering
		return 1;

	//case WM_CLOSE:
	//	break;

	case WM_PAINT:
		return 1;

	case WM_SIZE:
		{
			if(LOWORD(lParam) != width || HIWORD(lParam) != height)
			{
				SetWindowDimensions(LOWORD(lParam), HIWORD(lParam));
				return 0;
			}
		}
		break;

	//case WM_MOVE:
		//if (!ignoreMove)
		//	break;

	case WM_DESTROY:
		{
			// clear our entry so it is no longer accessed in CWindow::WndProc
			::SetWindowLongPtr(hWnd, 0, (LONG_PTR)nullptr);

			::ReleaseDC(handle, hDC);
			hDC = nullptr;
			handle = nullptr;

			eventHandler.Produce(EventType::WindowDestroyed, new WindowDestroyed(this));

			::PostQuitMessage(0);
			return 0;
		}

	//case WM_INPUT:
	//case WM_SHOWWINDOW:
	//case WM_SETTEXT:
	//case WM_ACTIVATE:
	//	return 0;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
	//return 0;
}

void CWindow::WindowCreate()
{
	try
	{
		hInstance = GetModuleHandle(NULL);

		dwStyle = WS_SYSMENU|WS_CAPTION|WS_THICKFRAME;
		dwExStyle = WS_EX_APPWINDOW;

		WNDCLASSEX wc = { 0 };
		wc.cbSize			= sizeof(WNDCLASSEX);
		wc.style			= CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
		wc.lpfnWndProc		= (WNDPROC)CWindow::WndProc;
		wc.cbClsExtra		= 0;
		wc.cbWndExtra		= sizeof(CWindow*);
		wc.hInstance		= hInstance;
		wc.hCursor			= (HCURSOR)::LoadImage(nullptr, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED);
		wc.hbrBackground	= nullptr;
		wc.lpszMenuName		= nullptr;
		wc.lpszClassName	= L"BattleBots";
		wc.hIcon = ::LoadIcon(nullptr, L"BATTLE_ICON");
		wc.hIcon = !wc.hIcon ? wc.hIcon = ::LoadIcon(nullptr, IDI_WINLOGO) : wc.hIcon;

		WNDCLASSEX wc2 = { 0 };
		if(!::GetClassInfoExW(hInstance, L"BattleBots", &wc2))
			if(!(classAtom = ::RegisterClassEx(&wc)))
				throw std::exception("Failed to register window class.");

		RECT workingArea = { 0 };
		if(::SystemParametersInfo(SPI_GETWORKAREA, 0, &workingArea, 0) == FALSE)
			throw std::exception("Failed to get working area info");

		RECT clientArea = { 0, 0, width, height };
		if(::AdjustWindowRectEx(&clientArea, dwStyle, FALSE, dwExStyle) == FALSE)
		{
			throw std::exception("Failed to adjust window rect");
		}

		handle = ::CreateWindowEx(
			dwExStyle, L"BattleBots", L"BattleBots", dwStyle,
			workingArea.right/2 - (clientArea.right-clientArea.left) / 2,
			workingArea.bottom/2 - (clientArea.bottom-clientArea.top) / 2,
			clientArea.right - clientArea.left,
			clientArea.bottom - clientArea.top,
			nullptr, nullptr, hInstance, this);

		if(handle == nullptr)
		{
			std::cout << "Error: `" << ::GetLastError() << "`" << std::endl;
			throw std::exception("Failed to create window");
		}

		if((hDC = ::GetDC(handle)) == nullptr)
		{
			std::cout << "Error: `" << ::GetLastError() << "`" << std::endl;
			throw std::exception("Failed to get device context");
		}

		::ShowWindow(handle, SW_SHOW);
		::UpdateWindow(handle);
	}
	catch(const std::exception& ex)
	{
		throw ex;
	}
}

void CWindow::WindowDestroy()
{
	if(handle != nullptr)
	{
		if(hDC != nullptr)
		{
			::ReleaseDC(handle, hDC);
			hDC = nullptr;
		}
		::DestroyWindow(handle);
		handle = nullptr;
	}
}

void CWindow::GetWindowDimensions(int* sizeX, int* sizeY)
{
	*sizeX = width;
	*sizeY = height;
}

void CWindow::SetWindowDimensions(int sizeX, int sizeY)
{
	width = sizeX;
	height = sizeY;

	eventHandler.Produce(EventType::WindowResize, new WindowResize(width, height));
}

int CWindow::WindowThread(EventHandler& eventHandler)
{
	try
	{
		WindowCreate();
	}
	catch(const std::exception& ex)
	{
		std::cout << "Could not create window: " << ex.what() << std::endl;
		return 1;
	}

	eventHandler.Produce(EventType::WindowCreated, new WindowCreated(this));

	MSG msg = { 0 };
	BOOL ret = FALSE;

	while(ret = ::GetMessage(&msg, handle, 0, 0))
	{
		::TranslateMessage(&msg); 
		::DispatchMessage(&msg); 
	}

	return 0;
}
